/**
 *  @file JSONTokenizer.cpp
 */
#include "JSONTokenizer.h"
#include "../../convert/Convert.h"
#include "../../lang/IntCheck.h"

using namespace cppflib::io;

namespace cppflib
{

namespace json
{

JSONTokenizer::JSONTokenizer(void)
{
   this->lineNum = 1;
   this->pReader = NULL;
}

JSONTokenizer::JSONTokenizer(Reader *pReader)
{
   this->lineNum = 1;
   this->pReader = pReader;
   cppmempool::Retain(this->pReader);
}

JSONTokenizer::~JSONTokenizer(void)
{
   if (this->pReader)
      cppmempool::Release(this->pReader);
}

void JSONTokenizer::IncrementLineNumber()
{
   ++this->lineNum ;
}


Reader *JSONTokenizer::GetReader()
{
   return this->pReader;
}

int JSONTokenizer::GetLineNumber()
{
   return this->lineNum;
}

////////////////// helper functions for tokenizer //////////////
    
PRIVATE void __RewindOneChar(Reader &reader)
{
   reader.Skip(-1);
}

PRIVATE JSONTokenizer::tokentype_t __Char2TokenType(int c)
{
   switch(static_cast<wc_t>(c)) {
      case _S('{') : return JSONTokenizer::JN_OBJSTART;
      case _S('}') : return JSONTokenizer::JN_OBJEND;
      case _S('[') : return JSONTokenizer::JN_ARRAYSTART;
      case _S(']') : return JSONTokenizer::JN_ARRAYEND;
      case _S(':') : return JSONTokenizer::JN_COLON;
      case _S(',') : return JSONTokenizer::JN_COMMA;
   }

   return JSONTokenizer::JN_UNKNOWN;
}

/**
 *  Read 4 hex characters in big endian and convert them to a UTF-16 char
 */
PRIVATE bool __ScanUnicodeCharInHex(Reader &reader, int &outC)
{
   int c[4] = {0, 0, 0, 0};
   if ((c[0] = reader.Read()) < 0) return false;
   if ((c[1] = reader.Read()) < 0) return false;
   if ((c[2] = reader.Read()) < 0) return false;
   if ((c[3] = reader.Read()) < 0) return false;
    
   outC = convert::Char2Hex(static_cast<wc_t>(c[0]));
   outC <<= 4;
   outC |= convert::Char2Hex(static_cast<wc_t>(c[1]));
   outC <<= 4;
   outC |= convert::Char2Hex(static_cast<wc_t>(c[2]));
   outC <<= 4;
   outC |= convert::Char2Hex(static_cast<wc_t>(c[3]));
   return true;
}

/**
 *  Scan a string surrounded by a pair of double quotes
 */
PRIVATE bool __ScanString(Reader &reader, JSONTokenizer::tokentype_t &tokenType, CFString &token)
{
   bool isOK = false;

   do {
      int c = reader.Read();

      if (c == _S('"')) {
         tokenType = JSONTokenizer::JN_STRING;
         isOK = true;
         break;
      }
      else if (c == _S('\\')) {
         bool isEscapeCharOK = true;
         c = reader.Read();
         switch (c) {
            case _S('"'): token += _S('"'); break;
            case _S('\\'): token += _S('\\'); break;
            case _S('/'): token += _S('/'); break;
            case _S('b'): token.Delete(token.GetLength() - 1, 1); break;  // backspace
            case _S('f'): token += _S('\f'); break;  // page break char; rarely used
            case _S('n'): token += _S('\n'); break;
            case _S('r'): token += _S('\r'); break;
            case _S('t'): token += _S('\t'); break;
            case _S('u'): 
                  {
                     int outC = 0;
                     if (__ScanUnicodeCharInHex(reader, outC))
                        token += static_cast<wc_t>(outC); 
                     else 
                        isEscapeCharOK = false;
                  }
                  break;
            default: isEscapeCharOK = false; break;
         } // end of switch

         if (!isEscapeCharOK)
            break;
      }
      else if (c < 0 || (c >= 0 && c <= 31) || (c >= 128 && c <= 159)) { // control characters
         break;
      }
      else {
         token += static_cast<wc_t>(c);  // normal character
      }
   } while(true);

   return isOK;
}

PRIVATE bool __CollectNumber(Reader &reader, CFString &token)
{
   bool isOK = true;
   int c = 0;

   while(true) {
      c = reader.Read();
      if (c < 0) {
         isOK = false;
         break;
      }
      else if (c == _S(' ') || c == _S('\t') || c == _S('\r') || c == _S('\n') ||
               c == _S(',') || c == _S(']') || c == _S('}')) {
         __RewindOneChar(reader);
         break;
      }
      else {
         token += static_cast<wc_t>(c);
      }
   }

   return isOK;
}

/**
 *  Scan an 32bit integer, 64 bit integer or double
 */
PRIVATE bool __ScanNumber(Reader &reader, JSONTokenizer::tokentype_t &tokenType, CFString &token)
{
   if (!__CollectNumber(reader, token))  // collect 'possible' number to 'token'
      return false;

   switch(lang::CheckNumType(token)) {
      case lang::NUMT_SINT32: tokenType = JSONTokenizer::JN_INT32; break;
      case lang::NUMT_SINT64: tokenType = JSONTokenizer::JN_INT64; break;
      case lang::NUMT_DOUBLE: tokenType = JSONTokenizer::JN_DOUBLE; break;
      case lang::NUMT_SINT64OVERFLOW: tokenType = JSONTokenizer::JN_INT64OVERFLOW; break;
      default: tokenType = JSONTokenizer::JN_UNKNOWN; break;
   }
   
   return (tokenType != JSONTokenizer::JN_UNKNOWN);
}

/**
 *  Scan true, false or null
 */
PRIVATE bool __ScanTrueFalseNull(Reader &reader, JSONTokenizer::tokentype_t &tokenType, CFString &token)
{
   int c = 0;
   wc_t tempStr[6] = {0, 0, 0, 0, 0, 0};
   
   if ((c = reader.Read()) < 0) return false;
   tempStr[0] = static_cast<wc_t>(c);
   if ((c = reader.Read()) < 0) return false;
   tempStr[1] = static_cast<wc_t>(c);
   if ((c = reader.Read()) < 0) return false;
   tempStr[2] = static_cast<wc_t>(c);
   if ((c = reader.Read()) < 0) return false;
   tempStr[3] = static_cast<wc_t>(c);

   if (CFString::WStrcmp(tempStr, _S("true")) == 0) {   // true
      tokenType = JSONTokenizer::JN_TRUE;
      token = _S("true");
      return true;
   }
   else if (CFString::WStrcmp(tempStr, _S("null")) == 0) { // null
      tokenType = JSONTokenizer::JN_NULL;
      token = _S("null");
      return true;
   }

   if ((c = reader.Read()) < 0) return false;   // read one more character
   tempStr[4] = static_cast<wc_t>(c);

   if (CFString::WStrcmp(tempStr, _S("false")) == 0) {  // false
      tokenType = JSONTokenizer::JN_FALSE;
      token = _S("false");
      return true;
   }

   return false;
}

/**
 *  Scan comment 
 *  Comment can be single line (//) or wrapped around by (slash*) (*slash)
 */
PRIVATE bool __ScanComment(JSONTokenizer &tokenizer)
{
   bool isOK = true;
   Reader *pReader = tokenizer.GetReader();
   int c = pReader->Read();

   if (c == _S('/')) {  // single line comment
      while(true) {
         if ((c = pReader->Read()) < 0) { break; }
         if (c == _S('\n')) {
            tokenizer.IncrementLineNumber();
            break;
         }
      }
   }
   else if (c == _S('*')) {  // multiline comment
      while(true) {
         if ((c = pReader->Read()) < 0) { isOK = false; break; }
         if (c == _S('\n')) tokenizer.IncrementLineNumber();
         if (c != _S('*')) continue;
         if ((c = pReader->Read()) < 0) { isOK = false; break; }
         if (c != _S('/')) {
            __RewindOneChar(*pReader);   // in case there is a '**/' pattern
            continue;
         }
         break;
      }
   }
   else {
      isOK = false; // unknown
   }

   return isOK;
}

/**
 *  Collect unknown token, which would be reported to user
 */
PRIVATE void __CollectUnknownToken(Reader &reader, CFString &token)
{
      int c;
      while(true) {
         if ((c = reader.Read()) < 0) break; 
         if (c == _S(' ') || c == _S('\t') || c == _S('\r') || c == _S('\n') ||
             c == _S('{') || c == _S('}') || c == _S('[') || c == _S(']') ||
             c == _S(':') || c == _S(',') || c == _S('"') || c == _S('\'')) break;
         token += static_cast<wc_t>(c);
      }
}

/**
 *  Fetch a token
 *
 *  @param [out] token -- output token
 *
 *  @return type of token
 */
JSONTokenizer::tokentype_t JSONTokenizer::GetToken(CFString &token)
{
   JSONTokenizer::tokentype_t tokenType = JSONTokenizer::JN_UNKNOWN;
   bool isScanOK = false;

   token = "";  // clear first

   while(true) {
      int c = pReader->Read();

      if (c < 0) {
         isScanOK = true;
         tokenType = JSONTokenizer::JN_READEREND;
         token = "<End_Of_Reader>";
         break;
      }
      else if (c == _S(' ') || c == _S('\t') || c == _S('\r') || c == _S('\n')) {
         if (c == _S('\n')) this->IncrementLineNumber();
      }
      else if (c == _S('{') || c == _S('}') || c == _S('[') || c == _S(']') ||
               c == _S(':') || c == _S(',')) {
         isScanOK = true;
         tokenType = __Char2TokenType(c);
         token = static_cast<wc_t>(c);
         break;
      }
      else if (c == _S('"')) {
         isScanOK = __ScanString(*pReader, tokenType, token);
         break;
      }
      else if (c == _S('-') || (c >= _S('0') && c <= _S('9'))) {
         __RewindOneChar(*pReader);
         isScanOK = __ScanNumber(*pReader, tokenType, token);
         break;
      }
      else if (c == _S('t') || c == _S('f') || c == _S('n')) {
         __RewindOneChar(*pReader);
         isScanOK = __ScanTrueFalseNull(*pReader, tokenType, token);
         break;
      } 
      else if (c == _S('/')) {
         // scan comment
         if (!__ScanComment(*this)) {
            isScanOK = false;
            break;
         }
      }
      else {
         token = static_cast<wc_t>(c);
         __CollectUnknownToken(*pReader, token);
         break; // unknown
      }

   }  // end of while

   if (!isScanOK) {
      tokenType = JSONTokenizer::JN_UNKNOWN;
      //token = "<error>";
   }

   return tokenType;
}

} // end of namespace json

} // end of namespace cppflib
